package context;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import enums.ExecuteStateEnum;
import exception.SoiRuntimeException;
import lombok.*;
import model.ExecutionRecord;
import model.SoiNode;
import util.StringUtil;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @className: SoiContext
 * @author: ocxz1
 * @date: 2025/3/12 上午9:52
 * @Version: 1.0
 * @description: soi 上下文（当前执行）
 */
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SoiContext {

    /**
     * 来源ID
     */
    private Long sourceId;

    /**
     * 来源CODE
     */
    private String sourceCode;

    /**
     * 当前执行节点
     */
    private SoiNode currentSoiNode;

    /**
     * 当前执行状态枚举
     */
    private ExecuteStateEnum executeStateEnum = ExecuteStateEnum.NOT_STARTED;

    /**
     * 执行结果记录
     */
    private final List<ExecutionRecord> executionRecordList = new ArrayList<>();

    /**
     * 存放和业务关联的数据
     */
    private final Map<String, Object> businessDataMap = new ConcurrentHashMap<>();

    /**
     * 静态内部类，保存SoiContext所有字段信息
     */
    private static class AllFieldHolder {
        private static final List<Field> INSTANT_FIELD_LIST = ListUtil.toList(ReflectUtil.getFields(SoiContext.class));
    }

    /**
     * 添加推送业务数据
     *
     * @param key          key
     * @param businessData 数据
     */
    public void putBusinessData(String key, Object businessData) {
        businessDataMap.put(key, businessData);
    }

    /**
     * 添加推送业务数据
     *
     * @param businessData 数据
     */
    public void putBusinessData(Object businessData) {
        String key = businessData.getClass().getSimpleName();
        key = StringUtil.getFirstLowerCase(key);
        businessDataMap.putIfAbsent(key, businessData);
    }

    /**
     * 初始化，并且添加到业务数据
     *
     * @param tClass
     * @param <T>
     * @return
     */
    public <T> T initAndPutBusinessData(Class<T> tClass) {
        try {
            // 检查缓存中是否已经有该类型的实例
            if (businessDataMap.containsKey(tClass.getSimpleName())) {
                return (T) businessDataMap.get(tClass.getSimpleName());
            }

            // 如果没有找到，则创建一个新的实例
            T instance = tClass.getDeclaredConstructor().newInstance();

            // 将新创建的实例放入缓存中
            businessDataMap.put(tClass.getSimpleName(), instance);

            return instance;
        } catch (Exception e) {
            throw new SoiRuntimeException("Failed to create an instance of " + tClass.getName(), e);
        }
    }

    /**
     * 获取业务数据
     * @param key 获取业务数据KEY
     * @return 返回业务数据
     */
    public <T> T getBusinessData(String key, Class<T> clazz) {
        Object o = businessDataMap.get(key);
        if (Objects.isNull(o)) {
            return null;
        }

        try {
            if (clazz == null) {
                return (T) o;
            }

            return clazz.cast(o);
        } catch (Exception e) {
            // 抛出类型转换异常
            throw new SoiRuntimeException("2084_class_cast_exception");
        }
    }

    /**
     * 根据类型获取上下文对象
     * @param tClass 获取类型
     * @param beanNames bean名称，可以指定多个
     * @return bean数据
     * @param <T> 指定类型泛型
     */
    public <T> T getContextBeanByClass(Class<T> tClass, String...beanNames) {
        if (Objects.isNull(tClass)) {
            throw new SoiRuntimeException("参数params不允许为空");
        }

        List<Field> allFieldList = AllFieldHolder.INSTANT_FIELD_LIST;

        // 过滤出指定类型的class
        List<Field> fieldList = allFieldList.stream().filter(field -> tClass.isAssignableFrom(field.getType()))
                .collect(Collectors.toList());

        // 业务值列表
        Map<String, Object> filterMap = new ConcurrentHashMap<>();
        for (String key : businessDataMap.keySet()) {
            if (tClass.isAssignableFrom(businessDataMap.get(key).getClass())) {
                filterMap.put(key,businessDataMap.get(key));
            }
        }

        if (CollectionUtil.isEmpty(fieldList) && CollectionUtil.isEmpty(filterMap)) {
//            throw new GushenRuntimeException("2084_CLASS_NOT_MATCH_EXCEPTION");
            return null;
        }

        Field field;

        // 根据传入的bean名称匹配，匹配不到和匹配多个都会异常
        if (!Objects.isNull(beanNames) && beanNames.length != 0) {
            fieldList = fieldList.stream().filter(f -> ArrayUtil.containsIgnoreCase(beanNames, f.getName())).collect(Collectors.toList());
            filterMap = MapUtil.filter(filterMap, beanNames);

            // 判断是否重复
            if (fieldList.size() > 1 || filterMap.size() > 1 || (CollectionUtil.isNotEmpty(fieldList) && CollectionUtil.isEmpty(filterMap))) {
                throw new SoiRuntimeException("类型匹配多个，请检查！");
            }

            // 判断是否都为空
            if (CollectionUtil.isEmpty(fieldList) && CollectionUtil.isEmpty(filterMap)) {
//                throw new GushenRuntimeException("2084_CLASS_NOT_MATCH_EXCEPTION");
                return null;
            }
        }

        // 从符合条件的list中获取第一个，有的话，直接返回
        if (CollectionUtil.isNotEmpty(fieldList)) {
            field = fieldList.get(0);

            // 反射获取，并且类型转换
            try {
                field.setAccessible(true);
                Object o = field.get(this);
                return tClass.cast(o);
            } catch (IllegalAccessException e) {
                throw new SoiRuntimeException("类型转换异常，请检查！");
            } finally {
                field.setAccessible(false);
            }
        }

        // 从符合条件的map中获取第一个，有的话，直接返回
        if (CollectionUtil.isNotEmpty(filterMap)) {
            Object o = filterMap.values().toArray()[0];
            try {
                return tClass.cast(o);
            }catch (Exception e) {
                throw new SoiRuntimeException("类型转换异常，请检查！");
            }
        }

        // 走到这里，说明没有匹配到
        return null;
//        throw new GushenRuntimeException("2084_CLASS_NOT_MATCH_EXCEPTION");
    }

    public void putExecutionRecord(ExecutionRecord executionRecord) {
        executionRecordList.add(executionRecord);
    }

    public ExecutionRecord getAndPutExecutionRecord() {
        ExecutionRecord record = executionRecordList.stream().filter(executionRecord -> Objects.equals(executionRecord.getExecuteState(),
                currentSoiNode.getNodeCode())).findFirst().orElse(null);
        if (Objects.isNull(record)) {
            record = ExecutionRecord.builder()
                    .soiNodeCode(currentSoiNode.getNodeCode())
                    .chaneKey(currentSoiNode.getChineKey())
                    .executeUnitIdentifier(currentSoiNode.getSoiUnit().getUnitIdentifier())
                    .sourceCode(sourceCode)
                    .executeState(executeStateEnum.code)
                    .startTime(new Date())
                    .build();
            executionRecordList.add(record);
        }
        return record;
    }
}
